package vd

import (
	"io"
	"fmt"
	"bufio"
	"errors"
	"strings"
	"strconv"
	"math/big"
	"io/ioutil"
	"encoding/base64"
	"kumachan/standalone/ctn"
	"kumachan/standalone/util"
)


type deserializeContext struct {
	*Deserializer
	Depth   uint
	Parent  *parentObjectHandle
}
type deserializeReader struct {
	*bufio.Reader
	unreadIndention  uint
	linesRead        uint
}

func Deserialize(input io.Reader, expected *Type, deserializer *Deserializer) (Object, *Type, error) {
	// Context
	var ctx = deserializeContext {
		Deserializer: deserializer,
		Depth:        0,
	}
	// Buffered Reader
	var buffered_input = bufio.NewReader(input)
	// Read Header
	line, _, err := util.WellBehavedFscanln(buffered_input)
	if err != nil { return nil, nil, err }
	// Validate Header
	if line != header { return nil, nil, errors.New("invalid header") }
	// Read Type
	type_str, _, err := util.WellBehavedFscanln(buffered_input)
	if err != nil { return nil, nil, err }
	// Validate Type (1)
	var t, ok = TypeParse(type_str)
	if !(ok) { return nil, nil, fmt.Errorf("invalid type: %s", type_str) }
	if expected != nil {
		if !(TypeEqual(t, expected)) {
			return nil, nil, fmt.Errorf(
				"type not matching: expect %s but got %s",
				expected, t,
			)
		}
	}
	// Specialized Reader
	var reader = &deserializeReader {
		Reader: buffered_input,
		unreadIndention: 0,
		linesRead: 2,
	}
	// Object Deserialization
	obj, err := deserialize(reader, t, ctx)
	var n = (reader.linesRead + 1)
	if err != nil { return nil, nil,
		fmt.Errorf("error near line %d: %w", n, err) }
	return obj, t, nil
}

func deserialize(input *deserializeReader, t *Type, ctx deserializeContext) (Object, error) {
	// read a line
	line, _, err := readLine(input)
	if err != nil { return nil, err }
	// cut line into 2 parts: key and inline value
	var key, inline, _ = strings.Cut(line, " ")
	inline = strings.TrimSpace(inline)
	// get type from context if necessary
	if t == nil {
		var type_of_key, ok = ctx.Parent.GetType(key)
		if !(ok) {
			return nil, fmt.Errorf("invalid key: %s", key)
		}
		t = type_of_key
		// write back key
		ctx.Parent.Return(key)
	}
	// Validate Type (2)
	err = checkSupport(t, ctx.Deserializer)
	if err != nil { return nil, err }
	// Main Logic
	switch t.kind {
	case Bool:
		switch inline {
		case "true":  return ctx.ReadBool(true), nil
		case "false": return ctx.ReadBool(false), nil
		default:      return nil, errors.New("invalid bool")
		}
	case Float:
		value, err := strconv.ParseFloat(inline, 64)
		if err != nil {
			return nil, fmt.Errorf("invalid float: %w", err)
		}
		return ctx.ReadFloat(value), nil
	case Complex:
		value, err := strconv.ParseComplex(inline, 128)
		if err != nil {
			return nil, fmt.Errorf("invalid complex: %w", err)
		}
		return ctx.ReadComplex(value), nil
	case Integer:
		var value big.Int
		var _, ok = value.SetString(inline, 10)
		if !(ok) { return nil, errors.New("invalid int") }
		var obj, fit = ctx.ReadInteger(&value)
		if fit {
			return obj, nil
		} else {
			return nil, errors.New("integer too big")
		}
	case String:
		value, err := strconv.Unquote(inline)
		if err != nil { return nil, fmt.Errorf("invalid string: %w", err) }
		return ctx.ReadString(value), nil
	case Binary:
		var str_reader = strings.NewReader(inline)
		var decoder = base64.NewDecoder(base64.StdEncoding, str_reader)
		value, err := ioutil.ReadAll(decoder)
		if err != nil { return nil, fmt.Errorf("invalid binary: %w", err) }
		return ctx.ReadBinary(value), nil
	case Array:
		var array = ctx.CreateArray(t.elementType)
		var i = 0
		for {
			n, err := readIndent(input)
			if err != nil { return nil, err }
			if n == (ctx.Depth + 1) {
				var item_ctx = deserializeContext {
					Deserializer: ctx.Deserializer,
					Depth:        (ctx.Depth + 1),
				}
				item, err := deserialize(input, t.elementType, item_ctx)
				if err != nil { return nil, err }
				ctx.AppendItem(&array, item)
				i += 1
			} else if n <= ctx.Depth {
				unreadIndent(input, n)
				return array, nil
			} else {
				return nil, errors.New("wrong indention")
			}
		}
	case Optional:
		if inline == "none" {
			return ctx.None(t.elementType), nil
		} else {
			n, err := readIndent(input)
			if err != nil { return nil, err }
			if n == (ctx.Depth + 1) {
				var inner_ctx = deserializeContext {
					Deserializer: ctx.Deserializer,
					Depth:        (ctx.Depth + 1),
				}
				inner, err := deserialize(input, t.elementType, inner_ctx)
				if err != nil { return nil, err }
				return ctx.Some(inner, t.elementType), nil
			} else {
				return nil, errors.New("wrong indention")
			}
		}
	case Record:
		return ctx.MakeRecord(t.identifier, func(types ctn.Map[string,*Type]) (ctn.Map[string,Object], error) {
			var handle = parentObjectHandle {
				GetType: types.Lookup,
			}
			var zero ctn.Map[string,Object]
			var m = ctn.MakeMutMap[string,Object](ctn.StringCompare)
			for {
				n, err := readIndent(input)
				if err != nil { return zero, err }
				if n == (ctx.Depth + 1) {
					var entry_ctx = deserializeContext {
						Deserializer: ctx.Deserializer,
						Depth:        (ctx.Depth + 1),
						Parent:       &handle,
					}
					value, err := deserialize(input, nil, entry_ctx)
					var field_name = handle.ReturnKey
					if err != nil { return zero, err }
					if m.Has(field_name) {
						return zero, fmt.Errorf(
							"duplicate field %s",
							field_name,
						)
					}
					m.Insert(field_name, value)
				} else if n <= ctx.Depth {
					unreadIndent(input, n)
					return m.Map(), nil
				} else {
					return zero, errors.New("wrong indention")
				}
			}
		})
	case Enum:
		return ctx.MakeEnum(t.identifier, func(types ctn.Map[string, *Type]) (string, Object, error) {
			var handle = parentObjectHandle {
				GetType: types.Lookup,
			}
			n, err := readIndent(input)
			if err != nil { return "", nil, err }
			if n == (ctx.Depth + 1) {
				var case_ctx = deserializeContext {
					Deserializer: ctx.Deserializer,
					Depth:        (ctx.Depth + 1),
					Parent:       &handle,
				}
				case_value, err := deserialize(input, nil, case_ctx)
				var case_key = handle.ReturnKey
				if err != nil { return "", nil, err }
				return case_key, case_value, nil
			} else {
				return "", nil, errors.New("wrong indention")
			}
		})
	default:
		panic("impossible branch")
	}
}

func readIndent(input *deserializeReader) (uint, error) {
	var n = uint(0) + input.unreadIndention
	input.unreadIndention = 0
	for {
		char, err := input.ReadByte()
		if err == io.EOF { return 0, nil }
		if err != nil { return 0, err }
		if char == indent {
			n += 1
		} else {
			err := input.UnreadByte()
			if err != nil { panic("something went wrong") }
			return n, nil
		}
	}
}
func unreadIndent(input *deserializeReader, n uint) {
	input.unreadIndention += n
}
func readLine(input *deserializeReader) (string, int, error) {
	line, n, err := util.WellBehavedFscanln(input)
	if err == nil {
		input.linesRead += 1
	}
	return line, n, err
}
func checkSupport(t *Type, deserializer *Deserializer) error {
	switch t.kind {
	case Array, Optional:
		return checkSupport(t.elementType, deserializer)
	case Enum, Record:
		var ok = deserializer.CheckSupport(t.identifier, t.kind)
		if !(ok) {
			return fmt.Errorf("unsupported type: %s", t)
		}
	}
	return nil
}

type parentObjectHandle struct {
	GetType     func(key string) (*Type, bool)
	ReturnKey   string
}
func (h *parentObjectHandle) Return(key string) {
	h.ReturnKey = key
}


